#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<cstdlib>
using namespace std;

const int basesize=1024;
const int argvnum = 64;
//command line parameter list 
char *gargv[argvnum];

//for counting
int gargc;

//overall situation's The working path of the current shell
char Pwd[basesize];
//overall situation's Pwd envitonment variable
char Pwdenv[basesize];

//my env array 
const int envnum = 64;
char *genv[envnum];
//overall situation's variable
int lastcode = 0;


//get user name 
string GetUserName()
{
  string name =getenv("USER");
  return name.empty()?"None":name;
}

//get hostname 
string GetHostName()
{
  string HostName=getenv("HOSTNAME");
  return HostName.empty()?"None":HostName;
}

//get pwd 
string GetPwd()
{
  //string Pwd=getenv("PWD");
  if(nullptr == getcwd(Pwd,sizeof(Pwd))) return "None";
  snprintf(Pwdenv,sizeof(Pwdenv),"PWD=%s",Pwd);
  putenv(Pwdenv);
  return Pwd;
}

//Create output format 
string MakeCommandLine()
{
  //[newuser@hcss-ecs-e091 myshell]$
  char Command_Line[basesize];
  //output
  snprintf(Command_Line,basesize,"[%s@%s %s]# ",GetUserName().c_str(),GetHostName().c_str(),GetPwd().c_str());
  return Command_Line;
}

void PrintCommandLine()  //1.use this func to print command line
{
  //create command line 
  //this printf have no \n,so this result is won't be display immediately
  printf("%s",MakeCommandLine().c_str());
  //refresh screen
  fflush(stdout);
}

//after only call this commandline,put string to this buffer
bool GetCommandLine(char Command_Buffer[],int size)    //2.get user's command 
{
  //cannot be use scanf and cin 
  //we think:We need to treat the command line entered by the user as a complete string 
  //"ls -a -l -n" this is a complete string 
  //              array        size   stadard input
  char *result = fgets(Command_Buffer,size,stdin);
  if(!result) return false;
  //we should delete last str,because it is enter key
  Command_Buffer[strlen(Command_Buffer)-1] = 0;//currently, it is OK
  if(strlen(Command_Buffer) == 0) return false;
  return true;
}

void ParseCommandLine(char Command_Buffer[],int len)  //3.analyze command
{
  (void)len;
  memset(gargv,0,sizeof(gargv));
  gargc=0;
  //"ls -a -l -n"---->ls
  //finally cut to Cogargv 
  const char *sep = " ";
  //post++
  gargv[gargc++] = strtok(Command_Buffer,sep);
  //Form a table and stop looping when the return value is nullptr 
  while((bool)(gargv[gargc++] = strtok(nullptr,sep)));
  gargc--;
}


//in my command line 
//have some command must be child process to implement
//but have some command not be child process to implement----built-in command 
bool ExecuteCommand()    //4.implement command
{
  //implement command 
  //let the child process execute 
  //because parent process execute the process ,if this process is failed ,myshell is hangs
  pid_t id = fork();//create child
  if(id < 0) return false;
  if(id == 0)
  {
    //child process
    //implement command 
    execvpe(gargv[0],gargv,genv);
    
    //Exit
    exit(1);//fail return 1
  }

  int status = 0;
  pid_t rid = waitpid(id,&status,0);//blocking wait 
  if(rid > 0)
  {
    if(WIFEXITED(status))
    {
      //get error code 
      lastcode = WEXITSTATUS(status);
    }
    else 
    {
      //code not running have abnormal 
      lastcode = 100;
    }
    //wait success
    return true;
  }
  else return false;
}

//add a environment variable 
void AddEnv(const char *item)
{
  int index = 0;
  while(genv[index]) index++;

  //find last location 
  genv[index] = (char*)malloc(strlen(item)+1);
  strncpy(genv[index],item,strlen(item)+1);
  genv[++index] = nullptr;

}

//shell execute command by itself,the essence is shell call itself's func
bool CheckAndExecBuildCommand()//check build-in command and execute command 
{
  if(strcmp(gargv[0],"cd") == 0)
  {
    //build-in command
    if(gargc == 2)
    {
      //change path 
      chdir(gargv[1]);
    }
    else 
    {
      lastcode = 1;
    }
    return true;
  }
  //export is also a build-in command 
  else if(strcmp(gargv[0],"export") == 0)
  {
    if(gargc == 2)
    {
      AddEnv(gargv[1]);
    }
    else 
    {
      //let error code be set to 1
      lastcode = 2;
    }
    return true;
  }
  //env is also a build-in command 
  else if(strcmp(gargv[0],"env") == 0)
  {
    for(int i = 0;genv[i];i++)
    {
      printf("%s\n",genv[i]);
    }
    lastcode = 0;
    return true;
  }
  else if(strcmp(gargv[0],"echo") == 0)
  {
    if(gargc == 2)
    {
      //echo $?
      //echo ehllo
      //if gargv[1][0] is $,this situation indicete it is find some kind of variable 
      if(gargv[1][0] == '$')
      {

      }
    }
    else 
    {
      lastcode = 3;
    }
  }
  return false;
}


//as a shell,to get a evironment variable should from system to get 
//today, we direct get environment variable from parent process
void InitEnv()
{
  //get environment variables from parent process 
  extern char **environ;
  int index = 0;
  while(environ[index] != nullptr)
  {
    //open up the same space as environment variable 
    genv[index] =(char*)malloc(strlen(environ[index])+1);
    //copy element in genv to environ 
    strncpy(genv[index],environ[index],strlen(environ[index]+1));
    index++;
  }
  genv[index]=nullptr;
}


int main()
{
  //my shell's environment variable 
  InitEnv();
  //new buffer 
  char Command_Buffer[basesize];
  //because shell must keep running
  while(true)
  {
    PrintCommandLine();  //1.use this func to print command line
    //only get <command_buffer ->output
    if(!GetCommandLine(Command_Buffer,basesize))    //2.get user's command 
    {
      //get fail 
      continue; 
    }
    //"is -a -b -c -d"--->"ls" "-a" "-b" "-d"
    ParseCommandLine(Command_Buffer,strlen(Command_Buffer));  //3.analyze command
    if(CheckAndExecBuildCommand())
    {
      continue;
    }
    ExecuteCommand();    //4.implement command
  }
  return 0;
}
